import os
import ast
import numpy as np
import pandas as pd
import re
import subprocess
from sklearn.preprocessing import StandardScaler
from collections import defaultdict
from git import Repo
import requests
import argparse
from concurrent.futures import ThreadPoolExecutor
import time

class MultiLangFeatureExtractor:
    """多语言代码特征提取器：支持Python/Java/C++/JavaScript"""
    
    def __init__(self, sonar_url=None, sonar_auth=None):
        """
        初始化特征提取器
        :param sonar_url: SonarQube服务地址（用于质量特征提取）
        :param sonar_auth: SonarQube认证信息 (username, password)
        """
        self.sonar_url = sonar_url
        self.sonar_auth = sonar_auth
        self.supported_langs = {
            'py': self._extract_python_features,
            'java': self._extract_java_features,
            'cpp': self._extract_cpp_features,
            'js': self._extract_js_features
        }

    def _extract_python_features(self, code):
        """提取Python代码的静态结构特征"""
        try:
            tree = ast.parse(code)
            node_count = 0
            func_count = 0
            class_count = 0
            loop_count = 0
            cond_count = 0
            assign_count = 0

            for node in ast.walk(tree):
                node_count += 1
                if isinstance(node, ast.FunctionDef):
                    func_count += 1
                elif isinstance(node, ast.ClassDef):
                    class_count += 1
                elif isinstance(node, (ast.For, ast.While)):
                    loop_count += 1
                elif isinstance(node, ast.If):
                    cond_count += 1
                elif isinstance(node, ast.Assign):
                    assign_count += 1

            complexity = func_count + loop_count + cond_count
            return {
                'node_count': node_count,
                'func_count': func_count,
                'class_count': class_count,
                'loop_count': loop_count,
                'cond_count': cond_count,
                'assign_count': assign_count,
                'complexity': complexity,
                'loc': len(code.splitlines())
            }
        except Exception as e:
            print(f"Python特征提取失败: {str(e)}")
            return {}

    def _extract_java_features(self, code, file_path):
        """提取Java代码的静态结构特征（基于正则和语法分析）"""
        try:
            # 简化版Java特征提取（实际可集成ANTLR解析器）
            func_count = len(re.findall(r'public|private|protected\s+.*?\s+\w+\s*\([^)]*\)\s*\{', code, re.DOTALL))
            class_count = len(re.findall(r'public|private|protected\s+class\s+\w+', code))
            loop_count = len(re.findall(r'for\(|while\(|do\s+while', code))
            cond_count = len(re.findall(r'if\(', code))
            assign_count = len(re.findall(r'[a-zA-Z_$][a-zA-Z0-9_$]*\s*=', code))
            return {
                'func_count': func_count,
                'class_count': class_count,
                'loop_count': loop_count,
                'cond_count': cond_count,
                'assign_count': assign_count,
                'complexity': func_count + loop_count + cond_count,
                'loc': len(code.splitlines())
            }
        except Exception as e:
            print(f"Java特征提取失败: {str(e)}")
            return {}

    def _extract_cpp_features(self, code, file_path):
        """提取C++代码的静态结构特征（基于正则）"""
        try:
            func_count = len(re.findall(r'\w+\s*\([^)]*\)\s*\{', code, re.DOTALL))
            class_count = len(re.findall(r'class\s+\w+', code))
            loop_count = len(re.findall(r'for\(|while\(|do\s+while', code))
            cond_count = len(re.findall(r'if\(', code))
            assign_count = len(re.findall(r'[a-zA-Z_$][a-zA-Z0-9_$]*\s*=', code))
            return {
                'func_count': func_count,
                'class_count': class_count,
                'loop_count': loop_count,
                'cond_count': cond_count,
                'assign_count': assign_count,
                'complexity': func_count + loop_count + cond_count,
                'loc': len(code.splitlines())
            }
        except Exception as e:
            print(f"C++特征提取失败: {str(e)}")
            return {}

    def _extract_js_features(self, code, file_path):
        """提取JavaScript代码的静态结构特征"""
        try:
            func_count = len(re.findall(r'function\s*\w*\s*\([^)]*\)\s*\{|=\s*function\s*\([^)]*\)\s*\{', code, re.DOTALL))
            class_count = len(re.findall(r'class\s+\w+', code))
            loop_count = len(re.findall(r'for\(|while\(|do\s+while', code))
            cond_count = len(re.findall(r'if\(', code))
            assign_count = len(re.findall(r'[a-zA-Z_$][a-zA-Z0-9_$]*\s*=', code))
            return {
                'func_count': func_count,
                'class_count': class_count,
                'loop_count': loop_count,
                'cond_count': cond_count,
                'assign_count': assign_count,
                'complexity': func_count + loop_count + cond_count,
                'loc': len(code.splitlines())
            }
        except Exception as e:
            print(f"JS特征提取失败: {str(e)}")
            return {}

    def extract_static_features(self, file_path):
        """
        提取静态结构特征（自动识别语言）
        :param file_path: 代码文件路径
        :return: 静态特征字典
        """
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                code = f.read()
            ext = os.path.splitext(file_path)[1].lower()[1:]
            if ext in self.supported_langs:
                return self.supported_langs[ext](code, file_path)
            else:
                print(f"不支持的文件类型: {ext}")
                return {}
        except Exception as e:
            print(f"静态特征提取失败: {str(e)}")
            return {}

    def extract_dynamic_features(self, repo_path, file_name):
        """
        提取动态演化特征（基于Git历史）
        :param repo_path: 代码仓库根路径
        :param file_name: 目标文件名（相对路径）
        :return: 动态特征字典
        """
        try:
            repo = Repo(repo_path)
            commits = list(repo.iter_commits(paths=file_name))
            commit_count = len(commits)
            authors = {commit.author.email for commit in commits}
            author_count = len(authors)
            if commits:
                last_commit_time = commits[0].committed_datetime
                last_modify_days = (pd.Timestamp.now() - pd.Timestamp(last_commit_time)).days
                # 计算累计修改行数（简化版）
                change_lines = 0
                for commit in commits:
                    diff = commit.diff(None) if commit == commits[0] else commit.diff(commits[commits.index(commit)-1])
                    for d in diff:
                        if d.a_path == file_name or d.b_path == file_name:
                            change_lines += abs(d.deletions) + abs(d.insertions)
            else:
                last_modify_days = 0
                change_lines = 0

            return {
                'commit_count': commit_count,
                'author_count': author_count,
                'last_modify_days': last_modify_days,
                'change_lines': change_lines,
                'change_frequency': commit_count / max(1, last_modify_days // 30) if last_modify_days > 0 else 0
            }
        except Exception as e:
            print(f"动态特征提取失败: {str(e)}")
            return {}

    def get_sonar_metrics(self, project_key, file_path):
        """从SonarQube获取质量特征"""
        if not self.sonar_url:
            return {
                'defect_count': 0,
                'duplication_rate': 0.0,
                'coverage_rate': 0.0,
                'violation_count': 0
            }
        try:
            url = f"{self.sonar_url}/api/measures/component"
            params = {
                "component": project_key,
                "path": file_path,
                "metricKeys": "bugs,duplicated_lines_density,coverage,code_smells"
            }
            response = requests.get(url, params=params, auth=self.sonar_auth)
            response.raise_for_status()
            metrics = response.json().get("component", {}).get("measures", [])
            return {
                'defect_count': int([m['value'] for m in metrics if m['metric'] == 'bugs'][0]),
                'duplication_rate': float([m['value'] for m in metrics if m['metric'] == 'duplicated_lines_density'][0])/100,
                'coverage_rate': float([m['value'] for m in metrics if m['metric'] == 'coverage'][0])/100,
                'violation_count': int([m['value'] for m in metrics if m['metric'] == 'code_smells'][0])
            }
        except Exception as e:
            print(f"SonarQube指标获取失败: {str(e)}")
            return {
                'defect_count': 0,
                'duplication_rate': 0.0,
                'coverage_rate': 0.0,
                'violation_count': 0
            }

    def extract_quality_features(self, project_key, file_path):
        """提取质量相关特征"""
        return self.get_sonar_metrics(project_key, file_path)

    def process_file(self, repo_path, file_path, project_key=None):
        """
        处理单个文件，提取所有特征
        :param repo_path: 代码仓库根路径
        :param file_path: 目标文件绝对路径
        :param project_key: SonarQube项目标识
        :return: 合并后的特征字典
        """
        file_name = os.path.relpath(file_path, repo_path)
        static_feat = self.extract_static_features(file_path)
        dynamic_feat = self.extract_dynamic_features(repo_path, file_name)
        quality_feat = self.extract_quality_features(project_key, file_name) if project_key else {}
        
        combined = {}
        combined.update(static_feat)
        combined.update(dynamic_feat)
        combined.update(quality_feat)
        combined['file_path'] = file_path
        combined['file_name'] = file_name
        return combined

    def _process_file_wrapper(self, args):
        """线程池执行的包装函数"""
        repo_path, file_path, project_key = args
        return self.process_file(repo_path, file_path, project_key)

    def batch_extract(self, project_paths, project_keys=None, max_workers=4):
        """
        批量提取多个项目的特征
        :param project_paths: 项目路径列表
        :param project_keys: SonarQube项目标识列表（与project_paths一一对应）
        :param max_workers: 线程池大小
        :return: 特征DataFrame
        """
        if project_keys is None:
            project_keys = [None] * len(project_paths)
        features = []
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            tasks = []
            for repo_path, project_key in zip(project_paths, project_keys):
                for root, _, files in os.walk(repo_path):
                    for file in files:
                        ext = os.path.splitext(file)[1].lower()[1:]
                        if ext in self.supported_langs:
                            file_path = os.path.join(root, file)
                            tasks.append((repo_path, file_path, project_key))
            # 提交所有任务并获取结果
            for result in executor.map(self._process_file_wrapper, tasks):
                if result:
                    features.append(result)
        df = pd.DataFrame(features)
        # 特征归一化
        numeric_cols = df.select_dtypes(include=[np.number]).columns
        if not numeric_cols.empty:
            df[numeric_cols] = StandardScaler().fit_transform(df[numeric_cols].fillna(0))
        return df

def main():
    """命令行入口"""
    parser = argparse.ArgumentParser(description="多语言代码特征提取工具，用于缺陷预测模型")
    parser.add_argument('project_paths', nargs='+', help="目标项目路径列表")
    parser.add_argument('--sonar-url', help="SonarQube服务地址")
    parser.add_argument('--sonar-username', help="SonarQube用户名")
    parser.add_argument('--sonar-password', help="SonarQube密码")
    parser.add_argument('--output', default='code_features.csv', help="特征输出文件路径")
    parser.add_argument('--max-workers', type=int, default=4, help="线程池大小")
    parser.add_argument('--project-keys', nargs='*', help="SonarQube项目标识列表（与project_paths一一对应）")
    
    args = parser.parse_args()
    sonar_auth = (args.sonar_username, args.sonar_password) if args.sonar_username and args.sonar_password else None
    extractor = MultiLangFeatureExtractor(args.sonar_url, sonar_auth)
    
    start_time = time.time()
    feature_df = extractor.batch_extract(
        args.project_paths, 
        args.project_keys, 
        args.max_workers
    )
    feature_df.to_csv(args.output, index=False)
    print(f"特征提取完成，耗时 {time.time()-start_time:.2f} 秒，共 {len(feature_df)} 条数据，已保存至 {args.output}")

if __name__ == "__main__":
    main()